﻿using System;
using System.Windows;
using System.Windows.Data;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace StarTrooper2DGame
{
    /// <summary>
    /// A helper that managed a UIElementRenderer handling details like page layout and the on-screen keyboard.
    /// </summary>
    public class UIElementRendererHelper : IDisposable
    {
        // One SpriteBatch shared by all helpers
        private static SpriteBatch spriteBatch;

        // Used so we can track the page sliding up due to the on-screen keyboard pushing it up.
        private static readonly DependencyProperty FrameTransformProperty = DependencyProperty.Register(
            "FrameTransform",
            typeof(double),
            typeof(PhoneApplicationPage),
            new PropertyMetadata(OnFrameTransformChanged));

        // Raised whenever a page's FrameTransform changes
        private static void OnFrameTransformChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            // Get the page itself
            PhoneApplicationPage obj = source as PhoneApplicationPage;

            // Grab the helper from the page and update it's pageY value with the new value
            UIElementRendererHelper uierHelper;
            if (obj.State.ContainsKey("UIERHelper") && (uierHelper = obj.State["UIERHelper"] as UIElementRendererHelper) != null)
                uierHelper.pageY = (float)(double)e.NewValue;
        }

        // The page this helper is drawing
        private PhoneApplicationPage page;

        // The UIElementRenderer responsible for rendering the page
        private UIElementRenderer uiRenderer;

        // The page's Y transform
        private float pageY;

        /// <summary>
        /// Gets or sets whether the helper should draw the UIElementRenderer. You should turn this off
        /// if you turn off shared rendering without disposing the helper.
        /// </summary>
        public bool IsVisible { get; set; }

        /// <summary>
        /// Creates a new UIElementRendererHelper
        /// </summary>
        /// <param name="page">The page to be renderer.</param>
        /// <param name="timer">The timer to which the helper should attach for drawing.</param>
        public UIElementRendererHelper(PhoneApplicationPage page)
        {
            // Pages can only have one UIElementRendererHelper
            if (page.State.ContainsKey("UIERHelper"))
                throw new InvalidOperationException();

            // Store the page and timer
            this.page = page;

            // Start off visible assuming shared rendering is enabled
            IsVisible = true;

            // Create the UIElementRenderer
            CreateUIRenderer();

            // Hook the page's LayoutUpdated event so we can recreate the UIElementRenderer as necessary
            page.LayoutUpdated += PageLayoutUpdated;

            // Add us to the page's State
            page.State.Add("UIERHelper", this);

            // Create the necessary binding to track the page's FrameTransform
            PhoneApplicationFrame frame = (Application.Current as App).RootFrame;
            Binding b = new Binding("Y");
            b.Source = (frame.RenderTransform as System.Windows.Media.TransformGroup).Children[0] as System.Windows.Media.TranslateTransform;
            page.SetBinding(FrameTransformProperty, b);
        }

        ~UIElementRendererHelper()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Remove us from the page
            if (page.State.ContainsKey("UIERHelper"))
                page.State.Remove("UIERHelper");

            // Unhook the events
            page.LayoutUpdated -= PageLayoutUpdated;

            // Dispose the UIElementRenderer
            if (uiRenderer != null)
                uiRenderer.Dispose();

            // Clear our references to the page and timer
            page = null;

            // Suppress the finalizer if we're being disposed by the public Dispose method
            if (disposing)
                GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes the helper, unhooking all necessary events.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }

        /// <summary>
        /// Renders the UI into a texture. Should be called before starting to draw the scene as it is a render target
        /// change and therefore will clear the backbuffer contents.
        /// </summary>
        public void PreRender()
        {
            // Only draw if visible and the UIElementRenderer is non-null
            if (IsVisible && uiRenderer != null)
            {
                // Render the page to our texture
                uiRenderer.Render();
            }
        }

        /// <summary>
        /// Draws the UI to the screen. Requires PreRender to have been called.
        /// </summary>
        public void Draw()
        {
            // Only draw if visible and the UIElementRenderer is non-null
            if (IsVisible && uiRenderer != null)
            {
                // Make sure we have our shared SpriteBatch instance
                if (spriteBatch == null)
                    spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);

                // Draw the page to the screen
                spriteBatch.Begin();
                spriteBatch.Draw(uiRenderer.Texture, new Vector2(0, pageY), Color.White);
                spriteBatch.End();
            }
        }

        private void PageLayoutUpdated(object sender, EventArgs args)
        {
            // Recreate the UIElementRenderer anytime layout updates if the page size has changed
            CreateUIRenderer();
        }

        private void CreateUIRenderer()
        {
            // We can't have a UIElementRenderer unless the page has positive dimensions
            if (page.ActualWidth <= 0 && page.ActualHeight <= 0)
                return;

            // Cast the size to integers as that's what we really need
            int width = (int)page.ActualWidth;
            int height = (int)page.ActualHeight;

            // Check to see if the UIElementRenderer exists and is the proper size already
            if (uiRenderer != null &&
                uiRenderer.Texture != null &&
                uiRenderer.Texture.Width == width &&
                uiRenderer.Texture.Height == height)
                return;

            // Make sure to dispose of the UIElementRenderer if we're creating a new one
            if (uiRenderer != null)
                uiRenderer.Dispose();

            uiRenderer = new UIElementRenderer(page, width, height);
        }
    }

}
